<?php

abstract class ClassStructure_Abstract {

    public function __construct() {
    }
    
    /**
     * Gets the base layer object.
     *
     * @access public
     * @return object The base layer object.
     */
    public function __getObject() {
        return $this;
    }
    
    /**
     * Gets the class name.
     *
     * This method uses the PHP function get_class().
     * 
     * @access public 
     * @return string The class name of the base layer object.
     */
    public function __getClass() {
        return get_class($this);
    }
    
    /**
     * Provides a standard way to get the class name of an object, with consideration
     * for the ClassStructure classes.
     *
     * @static static
     * @access public
     * @param mixed A string or object to determine the class name.
     * @return string
     */
    static public function __getClassName($obj_class) {
        
        if(
            is_object($obj_class) && 
            ($obj_class instanceof ClassStructure_Abstract) &&
            (get_class($obj_class) != 'ClassStructure_Abstract')
        ) {
            return $obj_class->__getClass();
        }
        
        return get_class($obj_class);
        
    }
    
    /**
     * Gets the parent class name.
     *
     * This method uses the PHP function get_parent_class().
     * 
     * @access public 
     * @return string The parent class name of the base layer object.
     */
    public function __getParentClass() {
        return get_parent_class($this);
    }
    
    /**
     * Provides a standard way to get the parent class of an object, with consideration
     * for the ClassStructure classes.
     *
     * @static static
     * @access public
     * @param mixed A string or object to determine the parent class.
     * @return string
     */
    static public function __getParentClassName($obj_class) {
        
        if(is_object($obj_class) && ($obj_class instanceof ClassStructure_Abstract)) {
            return $obj_class->__getParentClass();
        }
        
        return get_parent_class($obj_class);
        
    }
    
    /**
     * Determines whether the supplied parameter is an instance of the target.
     *
     * This method uses the PHP operator instanceof.
     * 
     * @access public
     * @param string|object $mix_class The class name or object to compare.
     * @return boolean Whether the supplied parameter is an instance of the base
     *      layer object.
     */
    public function __isInstanceOf($mix_class) {
        
        // standardise parameter into a class name
        $str_class = is_object($mix_class)
            ? $this->__getClassName($mix_class)
            : (string) $mix_class;
        
        // normal case: use PHP's operator to return the instance comparison.
        return ($this instanceof $str_class);
        
    }
    
    /**
     * Determines whether the supplied parameter is a subclass of the base 
     * layer object.
     *
     * If the base layer object is an instance of this class, then resolve this
     * by invoking this same method on the base layer object.
     *
     * This method uses the PHP function is_subclass_of.
     * 
     * @access public
     * @param string|object $mix_class The class name or object to compare.
     * @return boolean Whether the supplied parameter is an instance of the target.
     */
    public function __isSubclassOf($mix_class) {
        
        // standardise parameter into a class name
        $str_class = is_object($mix_class)
            ? $this->__getClassName($mix_class)
            : (string) $mix_class;
        
        // normal case: use PHP's function to return the subclass comparison.
        return is_subclass_of($this, $str_class);
        
    }
    
    /**
     * Gets the class methods of the object.
     *
     * This method uses the PHP function get_class_methods().
     * 
     * @access public 
     * @param string $str_virtual_prefix The prefix to give methods that are not
     *      actually defined at compile time.
     * @return string The class name of the base layer object.
     */
    public function __getClassMethods($str_virtual_prefix = '@') {
        // normal case: use PHP's function to return the class methods.
        return get_class_methods($this);
        
    }
    
    /**
     * Gets the class method signatures of the object.
     *
     * This method uses the PHP function get_class_methods().
     * 
     * @access public 
     * @param string $str_virtual_prefix The prefix to give methods that are not
     *      actually defined at compile time.
     * @return string The class name of the base layer object.
     */
    public function __getMethodSignatures($str_virtual_prefix = '@') {
        
        $arr_methods = self::getMethodSignatures($this);
        
        $arr_signatures = array();
        foreach($arr_methods as $str_method => $arr_method) {
            $arr_signatures[$str_method] = $this->formatMethodSignature($str_method, $arr_method);
        }
        
        return $arr_signatures;
        
    }
    
    /**
     * Determines whether the named method exists within this object.
     *
     * @access public
     * @param string $str_method The method to check for existence.
     * @return boolean
     */
    abstract public function __hasMethod($str_method);
    
    /**
     * Gets the method signature of a Reflection function/method.
     *
     * This method merely prepends the method name to the front of the parameter
     * signature.
     *
     * Overwrite this method to have specific, more complex method signatures.
     *
     * @access protected
     * @param object $obj_method The Reflection function/method to extract the 
     *      signature.
     * @return string The method signature
     */
    protected function getMethodSignature(ReflectionFunctionAbstract $obj_method) {
        $str_method = $obj_method->name;
        return $str_method . '::' . $this->getParameterSignature($obj_method);
    }
    
    /**
     * Determines if the supplied method's parameter signature matches the 
     * parameter signature of the Reflection function/method.
     *
     * @access protected
     * @param string $str_method The method to base the signature eligibility.
     * @param object $obj_method The Reflection function/method to check
     * @return boolean
     */
    protected function matchesParameterSignature($str_method, ReflectionFunctionAbstract $obj_method) {
        
        // retrieve the method signature
        $str_method_signature = $this->getMethod($str_method);
        
        // extract just the parameter signature
        $str_parameter_signature = substr($str_method_signature, strpos($str_method_signature, '::')+2);
        
        // check equality
        return $str_parameter_signature === $this->getParameterSignature($obj_method);
        
    }
    
    /**
     * Gets the parameter signature of a Reflection function/method.
     *
     * This method gets each parameter's name from the Reflection class and then
     * creates a string from resulting parameter name collection.
     *
     * Overwrite this method to have specific, more complex method signatures.
     *
     * @access protected
     * @param object $obj_method The Reflection function/method to extract the 
     *      signature.
     * @return string The parameter signature
     */
    protected function getParameterSignature(ReflectionFunctionAbstract $obj_method) {
        
        $arr_parameters = array();
        foreach($obj_method->getParameters() as $obj_parameter) {
            $arr_parameters[] = $obj_parameter->getName();
        }
        
        // keep the signature readable, so just separate using a '|' character
        $str_signature = implode('|', $arr_parameters);
        
        return $str_signature;
        
    }
    
    /**
     * Parses an object for available methods and then generates a method
     * signature cache for later use.
     *
     * @access protected
     * @param object $obj The object to parse available method signatures.
     * @return array The array of available method signatures.
     */
    protected function parseAvailableMethods($obj) {
        
        $arr_methods = $this->getAvailableMethods($obj);
        
        foreach($arr_methods as $str_method => $obj_method) {
            $arr_methods[$str_method] = $this->getMethodSignature($obj_method);
        }
        
        return $arr_methods;
        
    }
    
    protected function getAvailableMethods($obj) {
        
        $arr_methods = array();
        
        // if the supplied object is an instance of this class, then get the
        // base layer of the supplied object
        if($obj instanceof self) {
            $obj = $obj->__getObject();
        }
        
        // get all the Reflection methods
        $obj_class = new ReflectionClass($obj);
        $arr_temp = $obj_class->getMethods();
            
        foreach($arr_temp as $str_key => $obj_method) {
            
            // only save public methods which are not the constructor or destructor
            if(
                $obj_method->isPublic() && 
                !$obj_method->isConstructor() &&
                !$obj_method->isDestructor()
            ) {
                $str_method = $obj_method->name;
                $arr_methods[$str_method] = $obj_method;
            }
        }
        
        return $arr_methods;
        
    }
    
    static public function getMethodSignatures($obj) {
        
        $arr_methods = array();
        
        // get all the Reflection methods
        $obj_class = new ReflectionClass($obj);
        $arr_temp = $obj_class->getMethods();
            
        foreach($arr_temp as $str_key => $obj_method) {
            
            // only save public methods which are not the constructor or destructor
            if(
                $obj_method->isPublic() && 
                !$obj_method->isConstructor() &&
                !$obj_method->isDestructor()
            ) {
                
                $str_method = $obj_method->name;
                
                $arr_parameters = array();
                foreach($obj_method->getParameters() as $obj_parameter) {
                    $arr_parameters[] = $obj_parameter->getName();
                }
                
                $str_visibility = 'public';
                if($obj_method->isProtected()) {
                    $str_visibility = 'protected';
                } else if($obj_method->isPrivate()) {
                    $str_visibility = 'private';
                }
                
                $bln_static = ($obj_method->isStatic()) ? true : false;
                
                $str_return = null;
                
                // keep the signature readable, so just separate using a ',' character
                $str_signature = implode(', ', $arr_parameters);
        
                $arr_methods[$str_method] = array(
                    'parameters' => $arr_parameters,
                    'visibility' => $str_visibility,
                    'static' => $bln_static,
                    'return' => $str_return
                );
            }
        }
        
        return $arr_methods;
        
    }
    
    protected function formatMethodSignature($str_method, $arr_method) {
        
        $str_signature = '';
        if(isset($arr_method['static']) && ($arr_method['static'] == true)) {
            $str_signature .= 'static ';
        } else {
            $str_signature .= '       ';
        }
        
        $str_parameters = implode(', $', $arr_method['parameters']);
        
        $str_signature .= str_pad($arr_method['visibility'], 7) . ' function ' . $str_method . '(';
        
        if(!empty($str_parameters)) {
            $str_signature .= '$' . $str_parameters;
        }
        $str_signature .= ')';
        
        if(!empty($arr_method['return'])) {
            $str_signature .= ' : ' . $arr_method['return'];
        }
        
        return $str_signature;
        
    }

}

?>
